/*
* Copyright (c) 2010 The Turing Project
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* Except as contained in this notice, the name(s) of the above
* copyright holders shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without prior
* written authorization.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
 */

package simulator;

public class Node {
    private static int _maxNodes = 1000;

    
    private Double      _mass;
    private Double      _radius;
    private Vector3D    _position = new Vector3D();
    private Vector3D    _oldVelocity = new Vector3D();
    private Vector3D    _acceleration = new Vector3D();
    private Vector3D    _velocity = new Vector3D();
    private Vector3D    _rebound = new Vector3D();
    private Boolean     _static;
    private Boolean     _traceVelocity;


    
    private Double       _distanceAmplitude;
    private Double      _repulsiveForce;
    
    private static int _nNodes = 0;
    private int _nodeID;
    private static Boolean _linked[][] = new Boolean[_maxNodes][_maxNodes];
    private static Double      _maxAcceleration;
    private static Double      _maxVelocity;    

    public Node() {
        if (_nNodes < _maxNodes) {
            _position.cartesianSet(0.0, 0.0, 0.0);
            _velocity.cartesianSet(0.0, 0.0, 0.0);
            _oldVelocity.cartesianSet(0.0, 0.0, 0.0);
            _acceleration.cartesianSet(0.0, 0.0, 0.0);
            _rebound.cartesianSet(0.0,0.0,0.0);
            _repulsiveForce = 1000.0;
            _static = false;
            _mass = 1.0;
            _radius = 15.0;
            _traceVelocity = false;

            _nodeID = _nNodes;
            _nNodes ++;
            
            for (int i = 0; i < _nNodes; i ++) {
                _linked[_nodeID][i] = false;
                _linked[i][_nodeID] = false;
            }
        }
    }

    /**
     * Indicates that the node is linked to the otherNode by an edge.
     * @param otherNode
     */
    public void linkTo (Node otherNode) {
        _linked[_nodeID][otherNode.getID()] = true;
        _linked[otherNode.getID()][_nodeID] = true;
    }

    /**
     * This method is true if the node is linked to the otherNode via an edge.
     * @param otherNode
     * @return
     */
    public Boolean linkedWith (Node otherNode) {
        return _linked[_nodeID][otherNode.getID()];
    }

    /**
     * Returns true if the node's scalar velocity will be traced.
     * @return
     */
    public Boolean traceVelocity() {
        return _traceVelocity;
    }

    /**
     * Specifies whether (true) or not (false) the node's scalar velocity will be traced.
     * @param traceVelocity
     */
    public void setTraceVelocity (Boolean traceVelocity) {
        _traceVelocity = traceVelocity;
    }
    
    /**
     * Returns the node's ID.
     * @return
     */
    public int getID() {
        return _nodeID;
    }

    /**
     * Sets the x, y, and z coordinates of the node.
     * @param x
     * @param y
     * @param z
     * @return
     */
    public void setScalarPosition(Double x, Double y, Double z) {
        _position.cartesianSet(x, y, z);
    }

    /**
     * Changes the node's position vector.
     * @param position
     */
    public void setVectorPosition(Vector3D position) {
        _position.vectorSet(position);
    }

    /**
     * Sets the x, y, and z components of the node velocity.
     * @param x
     * @param y
     * @param z
     * @return
     */
    public void setScalarVelocity(Double x, Double y, Double z) {
        _velocity.cartesianSet(x, y, z);
    }

    /**
     * Sets the mass of the node in kilograms.
     * @param mass
     */
    public void setMass(Double mass) {
        _mass = mass;
    }

    /**
     * Sets the radius of the link in meters.
     * @param radius
     */
    public void setRadius (Double radius) {
        _radius = radius;
    }

    /**
     * Adds the offset vector to the node's current position vector.
     * @param offset
     */
    public void shiftPosition(Vector3D offset) {
        _position.addVector(offset);
    }

    /**
     * When cartesianSet to true, the node will not move during the simulation.
     * @param value
     */
    public void setStatic(Boolean value) {
        _static = value;
    }
    /**
     * Returns the x(0), y(1), or z(2) component of the node's position.
     * @param coordinate
     * @return
     */
    public Double getPositionComponent(int coordinate){
        return _position.getComponent(coordinate);
    }

    /**
     * Returns the vector position of the node.
     * @return
     */
    public Vector3D getVectorPosition() {
        return _position;
    }

    /**
     * Returns the specified coordinate value for the node where x = 0, y = 1,
     * and z = 2.
     * @param coordinate
     * @return
     */
    public Double getCoordinate(int coordinate){
        return _position.getComponent(coordinate);
    }

    /**
     * Returns the radius of the node.
     * @return
     */
    public Double getRadius() {
        return _radius;
    }

    /**
     * Returns true if the node is static and does not move.
     * @return
     * @see #setStatic(java.lang.Boolean)
     */
    public Boolean isStatic () {
        return _static;
    }

    /**
     * Returns the scalar velocity of the node.
     * @return
     */
    public Double getScalarVelocity () {
        return _velocity.getAmplitude();
    }

    /**
     * Determines whetner the node is in contact with the other node and computes
     * the vector collision force felt by the node based on an elastic collision
     * model.
     * @param otherNode
     * @param timeIncrement
     */
    public void applyCollisionForce(Node otherNode, Double timeIncrement) {
        if (! _static & ! _linked[_nodeID][otherNode.getID()]) {
            Double reboundDistance = _radius + otherNode.getRadius();
            if (Math.abs(otherNode.getCoordinate(2) - getCoordinate(2)) < reboundDistance) {
                if (Math.abs(otherNode.getCoordinate(1) - getCoordinate(1)) < reboundDistance) {
                    if (Math.abs(otherNode.getCoordinate(0) - getCoordinate(0)) < reboundDistance) {
                        _rebound.vectorSet(_position);
                        _rebound.subtractVector(otherNode.getVectorPosition());
                        _distanceAmplitude = _rebound.getAmplitude();
                        if (_distanceAmplitude != 0.0 & _distanceAmplitude < reboundDistance) {
                            _rebound.normalize();
                            _rebound.multiplyBy(Math.abs(2 * _velocity.getDotProduct(_rebound)));
                            _velocity.addVector(_rebound);
                        }
                    }
                }
            }
        }        
    }

    /**
     * Applies a rebound force to the node when it comes in contact with the ground
     * which spans the arena at z = groundLevel.
     * @param groundLevel
     */
    public void applyGroundCollisionForce (Double groundLevel) {
        if (! _static) {
            if(getCoordinate(2) - groundLevel < _radius) {
                   _rebound.cartesianSet(0.0, 0.0, 1.0);
                   _rebound.multiplyBy(Math.abs(2 * _velocity.getDotProduct(_rebound)));
                   _velocity.addVector(_rebound);
            }
        }

    }

    /**
     * Applies a positive force over some timeIncrement to change the node's velocity.
     * @param force
     * @param timeIncrement
     */
    public void applyPositiveForce(Vector3D force, Double timeIncrement) {
        if (! _static) {
            Vector3D newForce = new Vector3D();
            newForce.vectorSet(force);
            newForce.divideBy(_mass);
            newForce.multiplyBy(timeIncrement);
            _velocity.addVector(newForce);
        }
    }

    /**
     * Applies a negative force over some timeIncrement to change the node's velocity.
     * @param force
     * @param timeIncrement
     */
    public void applyNegativeForce(Vector3D force, Double timeIncrement) {
        if (! _static) {
            Vector3D newForce = new Vector3D();
            newForce.vectorSet(force);
            newForce.divideBy(_mass);
            newForce.multiplyBy(timeIncrement);
            _velocity.subtractVector(newForce);
        }
    }

    /**
     * Applies friction to reduce the velocity of the node by multiplying it
     * by the frictionCoeficient and the timeInterval.
     * @param frictionCoeficient
     */
    public void applyFriction(Double frictionCoeficient, Double timeIncrement) {
        Double factor = 1.0 - frictionCoeficient * timeIncrement;
        _velocity.multiplyBy(factor);
    }

    /**
     * Moves the node over some timeIncrement based on it's current velocity.
     * @param timeIncrement
     */
    public void applyVelocity(Double timeIncrement) {
        if (! _static) {
            Vector3D offset = new Vector3D();
            offset.addVector(_velocity);
            offset.multiplyBy(timeIncrement);
            _position.addVector(offset);
        }
    }
}
